package com.ymx.darling.executor;

import com.ymx.darling.bean.sql.Statement;
import com.ymx.darling.executor.cache.Cache;
import com.ymx.darling.executor.cache.CacheManager;
import com.ymx.darling.log.Log;
import com.ymx.darling.transaction.Transaction;

import java.util.*;

/**
 * 带有缓存的执行器
 * @author 爱Java的小于
 */
public class CachingExecutor extends SimpleExecutor {
    /**
     * 缓存管理器
     */
    private final CacheManager cacheManager;

    public CachingExecutor(Transaction transaction, CacheManager cacheManager) {
        super(transaction);
        this.cacheManager = cacheManager;
    }


    /**
     * 从二级缓存中返回数据
     * @param statement statement
     * @param <E> E
     * @return List
     */
    private <E> List<E> getCache(Statement statement) {
        List<?> cacheData = null;
        Class<?> cacheNamespace = statement.getNamespace();
        Cache cache = this.getCacheByNamespace(statement);
        // 判断指定命名空间下的缓存存在
        if (Objects.nonNull(cache)) {
            cacheData = cache.get(statement.getSqlKey());
            // 判断缓存中存在数据 将缓存命中次数自增
            if (Objects.nonNull(cacheData)) {
                this.incrementCacheAccessCount(cacheNamespace);
            }
        }
        // 记录缓存命中率日志
        Log statementLog = statement.getLogger();
        if (statementLog.isDebugEnabled()) {
            double hitRatio = this.cacheManager.getCacheHitRatio(cacheNamespace);
            statementLog.debug(String.format("Cache Hit Ratio [%s]: %s", cacheNamespace.getName(), hitRatio));
        }
        return (List<E>) cacheData;
    }


    /**
     * 根据命名空间返回缓存
     * @param statement statement
     * @return Cache
     */
    private Cache getCacheByNamespace(Statement statement) {
        if (!statement.hasRelationProperty() && !statement.hasRelationProperty() && statement.isUseCache()) {
            Class<?> cacheNamespace = statement.getNamespace();
            return cacheManager.getCache(cacheNamespace);
        }
        return null;
    }


    /**
     * 向缓存放入缓存数据
     * @param statement statement
     * @param result 结果集数据
     */
    private void putCache(Statement statement, List<?> result) {
        Cache cache = this.getCacheByNamespace(statement);
        if (cache != null) {
            cache.put(statement.getSqlKey(), result);
        }
    }


    /**
     * 根据命名空间清除缓存
     * 如果没有指定命名空间 则 清除全部命名空间缓存
     * @param statement statement
     */
    private void clearCache(Statement statement) {
        Cache cache = this.getCacheByNamespace(statement);
        // 清除对应命名空间的缓存
        if (cache != null) {
            cache.clear();
        } else {
            if (statement.getNamespace() == null) {
                this.cacheManager.clearAll();
            }
        }
    }


    /**
     * 自增指定命名空间数据库的访问次数
     * @param namespace 缓存命名空间
     */
    public void incrementDatabaseAccessCount(Class<?> namespace) {
        this.cacheManager.incrementDatabaseAccessCount(namespace);
    }


    /**
     * 自增指定命名空间缓存命中次数
     * @param namespace 缓存命名空间
     */
    public void incrementCacheAccessCount(Class<?> namespace) {
        if (namespace != null) {
            this.cacheManager.incrementCacheAccessCount(namespace);
        }
    }


    @Override
    public <E> List<E> queryList(Statement statement) {
        if (statement.getNamespace() == null) {
            return super.queryList(statement);
        } else {
            // 统计查询次数
            this.incrementDatabaseAccessCount(statement.getNamespace());
            List<E> resultCache = this.getCache(statement);
            if (resultCache != null) {
                return resultCache;
            } else {
                // 当缓存没有命中调用目标执行器查询
                List<E> list = super.queryList(statement);
                this.putCache(statement, list);

                return list;
            }
        }
    }


    @Override
    public int update(Statement statement) {
        int update = super.update(statement);
        // 只有当更新行数大于0那么才会清除缓存
        if (update > 0) {
            // 判断没有指定更新的命名空间 则清除全部缓存
            if (statement.getNamespace() == null) {
                this.cacheManager.clearAll();
            } else {
                this.clearCache(statement);
            }
        }
        return update;
    }
}

